﻿using System.Security.Principal;
using Vote.Project001.Core.Framework.SessionValues;
using Vote.Project001.Core.Infrastructure;

namespace Vote.Project001.Core.Framework
{
    public class SessionManager : Singleton<SessionManager>
    {
        #region Config

        private static TimeSpan SessionLifeSpan => new TimeSpan(0, 0, 30, 0);


        #endregion

        #region Auto update field container

        private readonly AccessTokenContainer _accessToken = new AccessTokenContainer();
        public string AccessToken => _accessToken;

        #endregion

        private readonly Queue<Session> _sessions = new Queue<Session>();
        private readonly Dictionary<Guid, Session> _idToSessionDictionary = new Dictionary<Guid, Session>();
        private readonly Dictionary<string, Session> _userIdToSessionDictionary = new Dictionary<string, Session>();
        private readonly object _locker = new();

        internal void RegisterSession(Session session)
        {
            lock (_locker)
            {
                // Clean up session.
                while (_sessions.Count > 0)
                {
                    var s = _sessions.Peek();
                    if (s.Abandoned || DateTime.Now - s.LastUpdateTime > SessionLifeSpan)
                    {
                        _sessions.Dequeue();
                        _idToSessionDictionary.Remove(s.Id);
                        _userIdToSessionDictionary.Remove(s.UserId);
                    }
                    else
                    {
                        break;
                    }
                }

                // Check whether user has been log in.
                if (_userIdToSessionDictionary.ContainsKey(session.UserId))
                {
                    // abandon current login info.
                    var prevSession = _userIdToSessionDictionary[session.UserId];
                    prevSession.Abandoned = true;
                    _idToSessionDictionary.Remove(prevSession.Id);
                    _userIdToSessionDictionary.Remove(prevSession.UserId);
                }

                // register new info.
                _sessions.Enqueue(session);
                _idToSessionDictionary.Add(session.Id, session);
                _userIdToSessionDictionary.Add(session.UserId, session);
            }
        }


        #region Public methods

        public Session CreateSession(string loginCode)
        {
            var session = Session.CreateSession(loginCode);
            RegisterSession(session);
            
            return session;
        }

        public Session? GetSession(string sessionId)
        {
            return GetSession(Guid.Parse(sessionId));
        }

        public Session? GetSession(Guid sessionId)
        {
            lock (_locker)
            {
                if (_idToSessionDictionary.ContainsKey(sessionId))
                {
                    var session = _idToSessionDictionary[sessionId];
                    session.LastUpdateTime = DateTime.Now;
                    return session;
                }
                else
                {
                    return null;
                }
            }
        }

        public Session? this[Guid id]
        {
            get
            {
                lock (_locker)
                {
                    if (_idToSessionDictionary.ContainsKey(id))
                    {
                        var session = _idToSessionDictionary[id];
                        session.LastUpdateTime = DateTime.Now;
                        return session;
                    }
                    else
                    {
                        return null;
                    }
                }
            }
        }

        #endregion

        #region Debug support

        internal class FakeUser
        {
            internal string Id { get; set; }
            internal string Name { get; set; }
            internal FakeUser(string id, string name)
            {
                Id = id;
                Name = name;
            }
        }
        private string _fakeUserCode = "FAKE USER ID";
        private FakeUser[] _fakeUsers=new FakeUser[]
        {
            new FakeUser("user-01","user-01"),
            new FakeUser("user-02","user-02"),
            new FakeUser("user-03","user-03"),
            new FakeUser("user-04","user-04"),
            new FakeUser("user-05","user-05"),
            new FakeUser("user-06","user-06"),
            new FakeUser("user-0&","user-0&"),
        };

        public void SetFakeUserCode(string code)
        {
            _fakeUserCode=code;
        }

        public string GetFakeUserCode()
        {
            return $"FAKE-ID-0{Random.Shared.Next(7):D1}";
        }

        internal FakeUser GetFakeUser(string userCode)
        {
            var index = Convert.ToInt32(userCode.Split('-')[2]);
            return _fakeUsers[index];
        }

        #endregion
    }
}
